package com.minimalist.common.config.mybatis;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.minimalist.common.entity.basic.vo.config.ConfigVO;
import com.minimalist.common.utils.CommonConstant;
import com.minimalist.common.utils.SafetyUtil;
import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.audit.AuditManager;
import com.mybatisflex.core.datasource.DataSourceDecipher;
import com.mybatisflex.core.dialect.DbType;
import com.mybatisflex.core.dialect.DialectFactory;
import com.mybatisflex.core.dialect.KeywordWrap;
import com.mybatisflex.core.dialect.LimitOffsetProcessor;
import com.mybatisflex.core.dialect.impl.CommonsDialectImpl;
import com.mybatisflex.core.dialect.impl.DmDialect;
import com.mybatisflex.core.tenant.TenantManager;
import jakarta.annotation.PostConstruct;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.minimalist.**.mapper")
public class MyBatisFlexConfiguration{

    private static final Logger logger = LoggerFactory.getLogger("mybatis-flex-sql");

    @Value("${spring.datasource.druid.db-type:mysql}")
    private String dbType;

    public MyBatisFlexConfiguration(){
        // 在构造函数中初始化基本配置，具体的数据库类型配置在@PostConstruct中处理
    }

    /**
     * 初始化数据库方言配置
     */
    @PostConstruct
    public void initDatabaseDialect(){
        DbType dialectType = getDbTypeFromConfig(dbType);

        // 根据不同数据库类型注册对应的方言
        switch (dialectType){
            case DM:
                DialectFactory.registerDialect(DbType.DM, new DmDialect());
                break;
            case GBASE_8S:
                DialectFactory.registerDialect(DbType.GBASE_8S
                        , new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION
                                , LimitOffsetProcessor.ORACLE));
            case MYSQL:
                // MySQL使用默认方言，无需特殊注册
                break;
            case POSTGRE_SQL:
                // PostgreSQL使用默认方言，无需特殊注册
                break;
            case ORACLE:
                // Oracle使用默认方言，无需特殊注册
                break;
            case SQLSERVER:
                // SQL Server使用默认方言，无需特殊注册
                break;
            default:
                logger.warn("未识别的数据库类型: {}, 使用默认配置", dbType);
                dialectType = DbType.MYSQL; // 默认使用MySQL
                break;
        }

        // 设置全局数据库类型
        FlexGlobalConfig.getDefaultConfig().setDbType(dialectType);
        logger.info("数据库方言配置完成，当前数据库类型: {}", dialectType);

        // 初始化审计和租户配置
        initAuditAndTenant();
    }

    /**
     * 根据配置字符串获取DbType枚举
     */
    private DbType getDbTypeFromConfig(String dbTypeStr){
        if (StrUtil.isBlank(dbTypeStr)){
            return DbType.MYSQL;
        }

        return switch (dbTypeStr.toLowerCase()){
            case "dm" -> DbType.DM;
            case "mysql" -> DbType.MYSQL;
            case "postgresql", "postgres" -> DbType.POSTGRE_SQL;
            case "oracle" -> DbType.ORACLE;
            case "sqlserver", "sql_server" -> DbType.SQLSERVER;
            case "h2" -> DbType.H2;
            case "sqlite" -> DbType.SQLITE;
            case "gbase8s" -> DbType.GBASE_8S;
            default -> {
                logger.warn("未识别的数据库类型配置: {}, 使用默认MySQL", dbTypeStr);
                yield DbType.MYSQL;
            }
        };
    }

    /**
     * 初始化审计和租户配置
     */
    private void initAuditAndTenant(){
        //开启审计功能
        AuditManager.setAuditEnable(true);
        //设置 SQL 审计收集器
        AuditManager.setMessageCollector(auditMessage -> {
            String sql = auditMessage.getFullSql();
            long elapsedTime = auditMessage.getElapsedTime();
            String sqlType = getSqlType(sql);
            String threadInfo = Thread.currentThread().getName();

            // 根据执行时间使用不同的日志级别
            if (elapsedTime > 1000){
                // 超过1秒的SQL记录为错误
                logger.error("[{}] 线程: {} | 类型: {} | SQL: {} | 执行时间: {} ms",
                        "慢SQL", threadInfo, sqlType, sql, elapsedTime);
            } else if (elapsedTime > 300){
                // 超过300毫秒的SQL记录为警告
                logger.warn("[{}] 线程: {} | 类型: {} | SQL: {} | 执行时间: {} ms",
                        "较慢SQL", threadInfo, sqlType, sql, elapsedTime);
            } else{
                // 正常SQL记录为提示
                logger.info("[{}] 线程: {} | 类型: {} | SQL: {} | 执行时间: {} ms",
                        "SQL", threadInfo, sqlType, sql, elapsedTime);
            }
        });

        //获取租户ID，目前支持返回一个租户ID
        TenantManager.setTenantFactory(() -> {
            //校验系统多租户是否开启
            ConfigVO configVO = CommonConstant.systemConfigMap.get(CommonConstant.SYSTEM_CONFIG_TENANT);
            if (ObjectUtil.isNotNull(configVO)){
                boolean tenantOnOff = Boolean.parseBoolean(configVO.getConfigValue());
                //忽略多租户
                if (!tenantOnOff){
                    //未打开，忽略多租户
                    return null;
                }
            }
            //如果是系统租户，并且cookie中携带其他租户ID参数，表示查询其他租户数据
            if (SafetyUtil.checkIsSystemTenant()){
                Long cookieTenantId = SafetyUtil.getCookieTenantId();
                if (ObjectUtil.isNotNull(cookieTenantId)){
                    //返回cookie中的租户ID
                    return new Object[]{cookieTenantId};
                }
            }
            //返回当前登陆人的租户ID
            return new Object[]{SafetyUtil.getLoginUserTenantId()};
        });
    }

    /**
     * 获取SQL类型
     *
     * @param sql SQL语句
     * @return SQL类型
     */
    private String getSqlType(String sql){
        if (StrUtil.isBlank(sql)){
            return "UNKNOWN";
        }

        sql = sql.trim().toUpperCase();
        if (sql.startsWith("SELECT")){
            return "SELECT";
        } else if (sql.startsWith("INSERT")){
            return "INSERT";
        } else if (sql.startsWith("UPDATE")){
            return "UPDATE";
        } else if (sql.startsWith("DELETE")){
            return "DELETE";
        } else if (sql.startsWith("CREATE")){
            return "CREATE";
        } else if (sql.startsWith("ALTER")){
            return "ALTER";
        } else if (sql.startsWith("DROP")){
            return "DROP";
        } else{
            return "OTHER";
        }
    }

    @Bean
    public DataSourceDecipher decipher(){
        return (_, value) -> value;
    }
}
